package vpc.ext.vstack;

import java.util.*;
import vpc.core.decl.Method;
import java.io.*;

import vpc.ext.vstack.util.DataHandler;
import vpc.ext.vstack.util.VstackException;

public class Node {

	//************* DATA *******************

	private static int idCounter = -1;

	final HashMap<String, Edge> edges;

	final String name;

	int stackPush;

	Node root;

	final int id;

	InterruptMask mask;

	//	************* STATIC CLASS METHODS *******************

	private static int getNewId() {
		idCounter++;
		return idCounter;
	}

	public static void pruneNode(Node n) {
		if (n == null) {
			return;
		}
		for (Edge e : n.edges.values()) {
			e = null;
		}
		n = null;
		return;
	}

	//	************* CONSTRUCTORS *******************

	// Root node constructor
	public Node(String name, int stackPush, vpc.hil.DeviceDecl device) {
		this.id = Node.getNewId();
		edges = new HashMap<String, Edge>();
		this.name = name;
		this.stackPush = stackPush;
		this.mask = new InterruptMask(device);
		root = this;
	}

	public Node(String name, int stackPush, Node root, InterruptMask mask) {
		this.id = Node.getNewId();
		edges = new HashMap<String, Edge>();
		this.name = name;
		this.stackPush = stackPush;
		this.mask = mask;
		this.root = root;
	}
	
	public Node(String name, int stackPush, InterruptMask mask) {
		this.id = Node.getNewId();
		edges = new HashMap<String, Edge>();
		this.name = name;
		this.stackPush = stackPush;
		this.mask = mask;
		this.root = null;
	}
	
	//	************* OBJECT METHODS *******************

	public void addEdge(Node n) {
		if (n == null) {
			return;
		}
		if (!edges.containsKey(n.name))
			this.edges.put(n.name, new Edge(this, n, n.stackPush));
	}

	public void removeEdge(Node n) {
		if (n == null) {
			return;
		}

		if (this.edges.containsKey(n.name)) {
			Edge e = this.edges.remove(n.name);
			e = null;
		}
		return;
	}

	public void changeRoot(Node root, Set<String> nodesSeen) {
		if (nodesSeen.contains(this.name))
			return;

		nodesSeen.add(this.name);

		this.root = root;

		for (Edge e : edges.values())
			e.changeRoot(root, nodesSeen);
	}

	/**
	 * Function to find a graph node by its string name.
	 * @param name The search string. Ex: the string returned by Method.getFullName()
	 * @param nodesSeen A list of Node(s) already discovered in the graph traversal.
	 * @return The Node matching the name (if found), null otherwise.
	 */
	public Node find(String searchName, Set<Node> nodesSeen) {
		//If this Node matches the name parameter, then return this
		if (this.name.equals(searchName))
			return this;

		//If this Node is part of the nodesSeen list, then a cycle exists.
		if (nodesSeen.contains(this))
			return null;
		else
			nodesSeen.add(this);
		// Else this Node has not been processed, add it.

		// Search each of the Edge(s) of the Node for a match as well.
		for (Edge e : edges.values()) {
			Node result = e.find(searchName, nodesSeen);

			if (result != null)
				return result;
		}

		return null;
	}

	public Node find(int searchId, Set<Node> nodesSeen) {

		//If this Node matches the name parameter, then return this
		if (this.id == searchId)
			return this;

		//If this Node is part of the nodesSeen list, then a cycle exists.
		if (nodesSeen.contains(this))
			return null;
		else
			nodesSeen.add(this);
		// Else this Node has not been processed, add it.

		// Search each of the Edge(s) of the Node for a match as well.
		for (Edge e : edges.values()) {
			Node result = e.find(searchId, nodesSeen);

			if (result != null)
				return result;
		}

		return null;
	}

	/**
	 * 
	 * @return A String representation of the Edge set of this Node.
	 */
	public String edgesToString() {
		StringBuffer buff = new java.lang.StringBuffer();
		for (Edge e : edges.values()) {
			buff.append(e.id);
			buff.append("\n\t");
		}
		return buff.toString();
	}

	public String toString() {
		StringBuffer buff = new java.lang.StringBuffer();
		buff.append("NODE : ");
		buff.append(this.name);
		buff.append("\n\t");
		buff.append(this.edgesToString());

		return buff.toString();
	}

	public void printGraph(PrintStream o, Set<Node> nodesSeen) {
		if (nodesSeen.contains(this.name))
			return;

		nodesSeen.add(this);

		o.println(this.toString());

		for (Edge e : edges.values())
			e.printGraph(o, nodesSeen);
	}

	/**
	 * This method returns the maximum stack size, which can be thought
	 * of as the sum of the edges of the longest path in the graph.
	 * @param nodesSeen A set of Nodes already seen on the path.
	 * @return The maximum stack push.
	 * @throws CycleException If a cycle is detected in the call graph.
	 */
	public int getMaxPush(Set<Node> nodesSeen)
			throws VstackException.CycleException {
		int highestPush = 0;
		if (nodesSeen.contains(this))
			throw new VstackException.CycleException();

		nodesSeen.add(this);

		for (Edge e : edges.values())
			highestPush = Math.max(highestPush, e.getMaxPush(nodesSeen));

		nodesSeen.remove(this);

		return stackPush + highestPush;

	}
}
